use libp2p::{identity, noise, tcp, yamux, Swarm, SwarmBuilder};
use std::{error::Error, time::Duration};
use tokio::sync::mpsc::{self, error::SendError};

use crate::p2p::mode::command::Command;

use super::{behaviour::Behaviour, event_loop::EventLoop};

pub struct Client {
    pub timeout: Duration,
    pub swarm: Swarm<Behaviour>,
    pub sender: mpsc::Sender<Command>,
    pub event_loop: EventLoop,
}

impl Client {
    pub fn new(secret_key_seed: Option<u8>, probe_interval: u64) -> Result<Self, Box<dyn Error>> {
        let id_keys = match secret_key_seed {
            Some(seed) => {
                let mut bytes = [0u8; 32];
                bytes[0] = seed;
                identity::Keypair::ed25519_from_bytes(bytes).unwrap()
            }
            None => identity::Keypair::generate_ed25519(),
        };
        let peer_id = id_keys.public().to_peer_id();
        let swarm = SwarmBuilder::with_existing_identity(id_keys)
            .with_tokio()
            .with_tcp(
                tcp::Config::default(),
                noise::Config::new,
                yamux::Config::default,
            )?
            .with_quic()
            .with_dns()?
            .with_behaviour(|key| Behaviour::new(key, peer_id, probe_interval))?
            .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(60)))
            .build();

        let (command_sender, command_receiver) = mpsc::channel(0);
        Ok(Self {
            swarm,
            sender: command_sender,
            event_loop: EventLoop::new(command_receiver),
            timeout: Duration::from_secs(60),
        })
    }

    pub fn set_timeout(&mut self, timeout: Duration) {
        self.timeout = timeout;
    }

    pub async fn send_command(&mut self, command: Command) -> Result<(), SendError<Command>> {
        self.sender.send(command).await
    }

    pub async fn run(&mut self) {
        self.event_loop.run(&mut self.swarm).await
    }
}
